using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using XNAGameEngine.GameState;
using XNAGameEngine.Logs;

namespace XNAGameEngine.Input
{
    /// <summary>
    /// Manager for grabbing input and throwing the input events
    /// </summary>
    public sealed partial class ManagerInput : GameComponent, IManagerInput
    {
        #region delegates

        /// <summary>
        /// delegate for registering/unregistering input event listeners
        /// </summary>
        /// <param name="listens">should the object listen</param>
        /// <param name="listener">the object to register/unregister</param>
        public delegate void InputRegisterListenerEventHandler(
            bool listens, object listener);

        /// <summary>
        /// delegate for input throwing
        /// </summary>
        /// <param name="input">a struct containing the input detected</param>
        private delegate void InputEventHandler(InputMessage input);

        #endregion

        #region events

        /// <summary>
        /// Event for input pressed
        /// </summary>
        private event InputEventHandler ThrowInputPressedEvent;

        /// <summary>
        /// Event for input held
        /// </summary>
        private event InputEventHandler ThrowInputHeldEvent;

        /// <summary>
        /// Event for input released
        /// </summary>
        private event InputEventHandler ThrowInputReleasedEvent;

        #endregion

        #region local fields

        /// <summary>
        /// The gamepad manager object. Manages the updating of the gamepad
        /// </summary>
        private readonly ManagerGamePadInput _gamepad;

        /// <summary>
        /// The keyboard manager object. Manages the updating of the keyboard
        /// </summary>
        private readonly ManagerKeyboardInput _keyboard;

        /// <summary>
        /// The mouse manager object. Manages the updating of the mouse
        /// </summary>
        private readonly ManagerMouseInput _mouse;

        /// <summary>
        /// The last input detected
        /// </summary>
        private InputMessage _lastInput;

        #endregion

        #region constructors

        /// <summary>
        /// Default constuctor saves a reference to the game engine
        /// </summary>
        /// <param name="gameEngine">the reference to the game engine</param>
        public ManagerInput(Game game)
            : base(game)
        {
            Game.Components.Add(this);
            Game.Services.AddService(typeof(IManagerInput), this);
            _keyboard = new ManagerKeyboardInput(this);
            _mouse = new ManagerMouseInput(this);
            _gamepad = new ManagerGamePadInput(this);
            _lastInput = new InputMessage();
            ManagerLogs.Write("ManagerInput: Created");
        }

        #endregion

        #region local methods

        /// <summary>
        /// Called by the individual input managers to throw events for buttons pressed. Two of the first
        /// three parameters will be null. Position will not be null whenever an input that requires a 
        /// value change is detected. (thumbstick changes and mouse movement are some examples. Triggers 
        /// only use a single float value and this will be passed in as the first parameter of position.
        /// </summary>
        /// <param name="key">the key pressed</param>
        /// <param name="button">the button pressed</param>
        /// <param name="mouse">the mouse input detected pressed</param>
        /// <param name="doubleVal">a single floating point value</param>
        /// <param name="vectorVal">a single vector value</param>
        public void InputPressed(
            Keys? key, Buttons? button, MouseClick? mouse, double? doubleVal,
            Vector2? vectorVal)
        {
            _lastInput._key = key;
            _lastInput._button = button;
            _lastInput._mouse = mouse;
            _lastInput._doubleVal = doubleVal;
            _lastInput._vectorVal = vectorVal;

            if (ThrowInputPressedEvent != null)
                ThrowInputPressedEvent(_lastInput);
        }

        /// <summary>
        /// Called by the individual input managers to throw events for buttons held. Two of the first
        /// three parameters will be null. Position will not be null whenever an input that requires a 
        /// value change is detected. (thumbstick changes and mouse movement are some examples. Triggers 
        /// only use a single float value and this will be passed in as the first parameter of position.
        /// </summary>
        /// <param name="key">the key pressed</param>
        /// <param name="button">the button pressed</param>
        /// <param name="mouse">the mouse input detected pressed</param>
        /// <param name="doubleVal">a single floating point value</param>
        /// <param name="vectorVal">a single vector value</param>
        public void InputHeld(
            Keys? key, Buttons? button, MouseClick? mouse, double? doubleVal,
            Vector2? vectorVal)
        {
            _lastInput._key = key;
            _lastInput._button = button;
            _lastInput._mouse = mouse;
            _lastInput._doubleVal = doubleVal;
            _lastInput._vectorVal = vectorVal;

            if (ThrowInputHeldEvent != null)
                ThrowInputHeldEvent(_lastInput);
        }

        /// <summary>
        /// Called by the individual input managers to throw events for buttons released. Two of the first
        /// three parameters will be null. Position will not be null whenever an input that requires a 
        /// value change is detected. (thumbstick changes and mouse movement are some examples. Triggers 
        /// only use a single float value and this will be passed in as the first parameter of position.
        /// </summary>
        /// <param name="key">the key pressed</param>
        /// <param name="button">the button pressed</param>
        /// <param name="mouse">the mouse input detected pressed</param>
        /// <param name="doubleVal">a single floating point value</param>
        /// <param name="vectorVal">a single vector value</param>
        public void InputReleased(
            Keys? key, Buttons? button, MouseClick? mouse, double? doubleVal,
            Vector2? vectorVal)
        {
            _lastInput._key = key;
            _lastInput._button = button;
            _lastInput._mouse = mouse;
            _lastInput._doubleVal = doubleVal;
            _lastInput._vectorVal = vectorVal;

            if (ThrowInputReleasedEvent != null)
                ThrowInputReleasedEvent(_lastInput);
        }

        #endregion

        #region GameComponent methods

        /// <summary>
        /// Polls the input devices for input actions
        /// </summary>
        /// <param name="gameTime">the game time object</param>
        public override void Update(GameTime gameTime)
        {
            _mouse.Update(gameTime); 
            if (!GameEngine.GetService<IManagerGameState>().IsCurrentStateActive)
                return;
            _keyboard.Update(gameTime);
            _gamepad.Update(gameTime);
            base.Update(gameTime);
        }

        #endregion

        #region IManagerInput Members

        /// <summary>
        /// Check if a key is currently pressed
        /// </summary>
        /// <param name="key">the key to check</param>
        /// <returns>true if the key is pressed, false if it is released</returns>
        public bool IsKeyPressed(Keys key)
        {
            return _keyboard._CurrentKeyboardState.IsKeyDown(key);
        }

        /// <summary>
        /// Returns if the key passed in was pressed this frame
        /// </summary>
        /// <param name="key">the key to check</param>
        /// <returns>true if the key was pressed this frame, false otherwise</returns>
        public bool WasKeyPressed(Keys key)
        {
            return _keyboard.WasKeyPressed(key);
        }

        /// <summary>
        /// Returns if the key passed in was released this frame
        /// </summary>
        /// <param name="key">the key to check</param>
        /// <returns>true if the key was released this frame, false otherwise</returns>
        public bool WasKeyReleased(Keys key)
        {
            return _keyboard.WasKeyReleased(key);
        }

        /// <summary>
        /// Returns if the key passed in was held this frame
        /// </summary>
        /// <param name="key">the key to check</param>
        /// <returns>true if the key was held this frame, false otherwise</returns>
        public bool WasKeyHeld(Keys key)
        {
            return _keyboard.WasKeyHeld(key);
        }

        /// <summary>
        /// <para>Change the input listening status of the object passed in to the bool value passed in.</para>
        /// <para>The object passed in MUST implement interface IManagerInputCallbacks</para>
        /// </summary>
        /// <param name="newListeningState">pass in true to have it listen to input, false to have it stop listening to input</param>
        /// <param name="newListener">the object whose listening status will be changed. Must implement interface "IManagerInputCallbacks"</param>
        public void ToggleInputListening(
            bool newListeningState, object listener)
        {
            if (!(listener is IInputListener))
                throw new SystemException(
                    "Object passed in does not implement interface IManagerInputCallbacks");

            IInputListener temp = listener as IInputListener;

            if (newListeningState)
            {
                ThrowInputPressedEvent += temp.InputPressed;
                ThrowInputHeldEvent += temp.InputHeld;
                ThrowInputReleasedEvent += temp.InputReleased;
                temp.ThrowUnregisterListenerEvent += ToggleInputListening;
                return;
            }

            ThrowInputPressedEvent -= temp.InputPressed;
            ThrowInputHeldEvent -= temp.InputHeld;
            ThrowInputReleasedEvent -= temp.InputReleased;
            temp.ThrowUnregisterListenerEvent -= ToggleInputListening;
        }

        #endregion
    }
}